package beautiful.butterfly.server.httpserver.mvc.http;

import beautiful.butterfly.server.httpserver.handlers.HttpConst;
import beautiful.butterfly.server.httpserver.handlers.HttpServer;
import beautiful.butterfly.server.httpserver.handlers.notchange.SessionHandler;
import beautiful.butterfly.server.httpserver.kit.StringKit;
import beautiful.butterfly.server.httpserver.mvc.multipart.FileItem;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.util.CharsetUtil;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.URLConnection;
import java.nio.file.Files;
import java.util.*;


@Slf4j
public class HttpRequest implements Request {
    //
    public static final String webRootPath = "webRootPath";
    private static final HttpDataFactory HTTP_DATA_FACTORY = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); // Disk if size exceed

    static {
        DiskFileUpload.deleteOnExitTemporaryFile = true;
        DiskFileUpload.baseDirectory = null;
        DiskAttribute.deleteOnExitTemporaryFile = true;
        DiskAttribute.baseDirectory = null;
    }

    private HttpServer httpServer;
    private SessionHandler sessionHandler;
    private ByteBuf body = Unpooled.copiedBuffer("", CharsetUtil.UTF_8);
    private String uri;
    private String url;
    private String method;

    private Map<String, String> headerMap = new HashMap<String, String>();
    private Map<String, Object> attributeMap = new HashMap<String, Object>();
    private Map<String, List<String>> parameterMap = new HashMap<>();
    private Map<String, Cookie> nameToCookieMap = new HashMap<String, Cookie>();
    private Map<String, FileItem> fileNameToFileItemMap = new HashMap<>();

    public HttpRequest(FullHttpRequest fullHttpRequest, HttpServer httpServer, SessionHandler sessionHandler) {
        this.httpServer = httpServer;
        this.attributeMap.put(HttpRequest.webRootPath, this.httpServer.getWebRootPath());
        this.url = fullHttpRequest.uri();
        int indexOf = this.url.indexOf('?');
        this.uri = indexOf < 0 ? this.url : this.url.substring(0, indexOf);
        //
        this.method = fullHttpRequest.method().name().toString();

        this.sessionHandler = sessionHandler;
        this.init(fullHttpRequest);

    }

    boolean isAjax() {
        return "XMLHttpRequest".equals(getHeader("x-requested-with"));
    }

    public boolean isKeepAlive() {
        return true;
    }

    private void init(FullHttpRequest fullHttpRequest) {
        // getHeaderMap
        HttpHeaders httpHeaders = fullHttpRequest.headers();
        if (httpHeaders.size() > 0) {
            for (Map.Entry entry : httpHeaders.entries()) {
                headerMap.put(entry.getKey().toString(), entry.getValue().toString());
                if (entry.getKey().equals(HttpConst.cookie)) {
                    String cookieString = entry.getValue().toString();
                    Cookie cookie = ClientCookieDecoder.decode(cookieString);
                    this.nameToCookieMap.put(cookie.name(), cookie);
                }
            }


        }

        // body
        this.body = fullHttpRequest.content().copy();

        //getParameterMap
        Map<String, List<String>> parameterMap = new QueryStringDecoder(fullHttpRequest.uri(), CharsetUtil.UTF_8).parameters();
        if (null != parameterMap) {
            this.parameterMap = new HashMap<>();
            this.parameterMap.putAll(parameterMap);
        }
        //parseInterfaceHttpData
        if (!HttpConst.METHOD_GET.equals(fullHttpRequest.method().name())) {
            HttpPostRequestDecoder httpPostRequestDecoder = new HttpPostRequestDecoder(HTTP_DATA_FACTORY, fullHttpRequest);
            Iterator<InterfaceHttpData> iterator = httpPostRequestDecoder.getBodyHttpDatas().iterator();
            while (iterator.hasNext()) {
                parseInterfaceHttpData(iterator.next());
            }
        }
        //putCookie
        String cookieString = getHeader("Cookie");
        if (cookieString != null) {
            cookieString = cookieString.length() > 0 ? cookieString : getHeader(HttpConst.COOKIE_STRING.toLowerCase());
            if (StringKit.isNotBlank(cookieString)) {
                Iterator<Cookie> iterator = ServerCookieDecoder.decode(cookieString).iterator();
                while (iterator.hasNext()) {
                    putCookie(iterator.next());
                }

            }
        }

    }


    private void parseInterfaceHttpData(InterfaceHttpData interfaceHttpData) {
        try {
            switch (interfaceHttpData.getHttpDataType()) {
                case Attribute:
                    Attribute attribute = (Attribute) interfaceHttpData;
                    String name = attribute.getName();
                    String value = attribute.getValue();
                    this.parameterMap.put(name, Collections.singletonList(value));
                    break;
                case FileUpload:
                    FileUpload fileUpload = (FileUpload) interfaceHttpData;
                    parseFileUpload(fileUpload);
                    break;
                default:
                    break;
            }
        } catch (IOException e) {
            log.error("parse getHttpRequest parameter error", e);
        } finally {
            interfaceHttpData.release();
        }
    }

    private void parseFileUpload(FileUpload fileUpload) throws IOException {
        if (fileUpload.isCompleted()) {
            String contentType = StringKit.mimeType(fileUpload.getFilename());
            if (null == contentType) {
                contentType = URLConnection.guessContentTypeFromName(fileUpload.getFilename());
            }
            if (fileUpload.isInMemory()) {
                FileItem fileItem = new FileItem(fileUpload.getName(), fileUpload.getFilename(),
                        contentType, fileUpload.length());
                fileItem.setData(fileUpload.getByteBuf().array());
                fileNameToFileItemMap.put(fileItem.getName(), fileItem);
            } else {
                FileItem fileItem = new FileItem(fileUpload.getName(), fileUpload.getFilename(),
                        contentType, fileUpload.length());
                byte[] bytes = Files.readAllBytes(fileUpload.getFile().toPath());
                fileItem.setData(bytes);
                fileNameToFileItemMap.put(fileItem.getName(), fileItem);
            }
        }
    }


    private void putCookie(Cookie cookie) {
        this.nameToCookieMap.put(cookie.name(), cookie);
    }

    public Map<String, Cookie> getNameToCookieMap() {
        return nameToCookieMap;
    }

    @Override
    public String getUri() {
        return this.uri;
    }

    @Override
    public String getUrl() {
        return this.url;
    }

    String getUserAgent() {
        return getHeader(HttpConst.USER_AGENT);
    }

    @Override
    public String getMethod() {
        return this.method;
    }

    @Override
    public Session getSession() {
        return this.sessionHandler.getOrNewSession(this);
    }

    @Override
    public Map<String, String> getClientNameToCookieMap() {
        Map<String, String> nameToCookieMap = new HashMap<>(this.nameToCookieMap.size());
        for (String key :
                this.nameToCookieMap.keySet()) {
            nameToCookieMap.put(key, this.nameToCookieMap.get(key).value());
        }
        return nameToCookieMap;
    }


    public Request setCookie(Cookie cookie) {
        this.nameToCookieMap.put(cookie.name(), cookie);
        return this;
    }


    @Override
    public Map<String, FileItem> getFileNameToFileItemMap() {
        return fileNameToFileItemMap;
    }

    FileItem getFileItem(@NonNull String fileName) {
        return getFileNameToFileItemMap().get(fileName);
    }


    //
    @Override
    public Map<String, List<String>> getParameterMap() {
        return parameterMap;
    }

    public String getParameter(@NonNull String name) {
        List<String> list = getParameterMap().get(name);
        if (null != list && list.size() > 0)
            return list.get(0);
        return null;
    }

    String query(@NonNull String name, @NonNull String defaultValue) {
        String value = getParameter(name);
        if (value != null) {
            return value;
        }
        return defaultValue;
    }

    Integer queryInt(@NonNull String name) {
        String value = getParameter(name);
        if (value != null) {
            return Integer.parseInt(value);
        }
        return null;
    }

    int queryInt(@NonNull String name, int defaultValue) {
        String value = getParameter(name);
        if (value != null) {
            return Integer.parseInt(value);
        }

        return defaultValue;
    }

    Long queryLong(@NonNull String name) {
        String value = getParameter(name);
        if (value != null) {
            return Long.parseLong(value);
        }
        return null;
    }

    long queryLong(@NonNull String name, long defaultValue) {
        String value = getParameter(name);
        if (value != null) {
            return Long.parseLong(value);
        }
        return defaultValue;
    }


    public String setCookie(@NonNull String name) {
        String value = getClientNameToCookieMap().get(name);
        if (value != null) {
            return value;
        }
        return null;
    }

    String setCookie(@NonNull String name, @NonNull String defaultValue) {
        String value = setCookie(name);
        if (value != null) {
            return value;
        }
        return defaultValue;
    }

    //
    public String getHeader(@NonNull String name) {
        return getHeaderMap().get(name);
    }


    String getHeader(@NonNull String name, @NonNull String defaultValue) {
        String value = getHeader(name);
        return value.length() > 0 ? value : defaultValue;
    }

    @Override
    public Map<String, String> getHeaderMap() {
        return this.headerMap;
    }


    public Request setAttribute(@NonNull String name, Object value) {

        if (null != value) {
            getAttributeMap().put(name, value);
        }
        return this;
    }

    @Override
    public Map<String, Object> getAttributeMap() {
        return this.attributeMap;
    }

    @Override
    public Object getAttribute(String name) {
        return getAttributeMap().get(name);
    }


}